Skip to content

[a11y] WCAG 4.1.2 — require label on CodeBlock primitive; add aria-readonly for read-only mode#3557

Open
rosanusi wants to merge 2 commits into
mainfrom
wcag/4.1.2-codemirror-no-name
Open

[a11y] WCAG 4.1.2 — require label on CodeBlock primitive; add aria-readonly for read-only mode#3557
rosanusi wants to merge 2 commits into
mainfrom
wcag/4.1.2-codemirror-no-name

Conversation

@rosanusi

Copy link
Copy Markdown
Contributor

Summary

  • Makes label: string required on the CodeBlock primitive (src/lib/holocene/code-block.svelte), applied to CodeMirror's .cm-content via EditorView.contentAttributes.of({ 'aria-label': label }) — every CodeMirror instance now has an accessible name
  • Adds aria-readonly="true" to contentAttributes when editable=false so AT (especially JAWS forms-mode) correctly conveys non-editability instead of announcing the element as an editable text field
  • Adds a dev-time onMount guard that throws (dev) / console.errors (prod) when label is empty at mount, catching dynamic-spread cases that TypeScript can't reach
  • Fixes the orphaned <Label for={id}> association in PayloadInput (§NEW1): replaces <Label for> with <span> and forwards label to the inner <CodeBlock> so the accessible name lands on the labelable .cm-content element
  • Migrates all ~28 affected files: primitive + 4 domain wrappers + ~22 direct consumers

Affected files

Primitive + wrappers (5 files):

  • src/lib/holocene/code-block.svelte
  • src/lib/components/payload/payload-code-block.svelte
  • src/lib/components/payload-input.svelte
  • src/lib/components/payload-input-with-encoding.svelte
  • src/lib/components/workflow/input-and-results-payload.svelte

Locale additions (5 files):

  • src/lib/i18n/locales/en/common.tsjson
  • src/lib/i18n/locales/en/events.tsimport-format-1, import-format-2
  • src/lib/i18n/locales/en/standalone-activities.tsactivity-input, activity-outcome, cluster-config, namespace-config
  • src/lib/i18n/locales/en/workers.tsheartbeat-config, setup-guide-command
  • src/lib/i18n/locales/en/workflows.tsquery-result, callback-metadata, update-input, update-result

Direct consumers (~23 files): event-card, workflow-error-stack-trace, schedules-calendar-view, schedule-frequency-panel, activity-input-and-outcome, standalone-activities-disabled, serverless-worker-setup-guide, worker-heartbeats-disabled, update-confirmation-modal, pending-activities, pending-activity-card, pending-nexus-operation-card, workflow-callback, workflow-json-navigator, import-events, standalone-activity-details, standalone-activity-search-attributes, workflow-call-stack, workflow-memo, workflow-metadata, workflow-query, workflow-search-attributes, archival/+page

Test plan

  • Screen reader: navigate to Workflow History Input/Result panels — .cm-content announces correct label; JAWS does not auto-switch to forms mode on read-only blocks
  • Screen reader: navigate to Workflow Queries query-arg input (editable) — announces label and allows typing; aria-readonly absent
  • Dev-mode enforcement: render <CodeBlock content="…" label="" /> — confirm it throws; add non-empty label — confirm no throw
  • Visual regression: all updated surfaces render identically to before; no visible change
  • pnpm check passes (no new type errors beyond pre-existing $$_$$ slot interop errors on main)
  • PayloadInput standalone (e.g. update-confirmation-modal): visible <span> label text renders correctly; CodeBlock has aria-label

A11y-Audit-Ref: 4.1.2-codemirror-no-name

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview, Comment Jun 12, 2026 8:25pm

Request Review

… read-only mode

Makes the `label` prop required on the CodeBlock primitive so every
CodeMirror instance gets an accessible name via `aria-label` on the
`.cm-content` element. Adds `aria-readonly="true"` when `editable=false`
so AT correctly conveys non-editability (JAWS forms-mode fix). Adds a
dev-time `onMount` guard that throws if label is empty after mount.

Migrates all ~28 affected files: primitive, 4 domain wrappers
(PayloadCodeBlock, PayloadInput, InputAndResultsPayload,
PayloadInputWithEncoding), and ~22 direct consumers across Workflow
History, Queries, Schedules, Activities, Events, Workers, and Archival.

Also fixes the PayloadInput orphaned <Label for> association (§NEW1):
replaces <Label for={id}> with <span> and forwards label to the inner
CodeBlock so the accessible name is set on the cm-content element rather
than a wrapper div that for= can't reach.

New i18n keys: common.json, events.import-format-{1,2},
standalone-activities.{activity-input,activity-outcome,cluster-config,
namespace-config}, workers.{heartbeat-config,setup-guide-command},
workflows.{query-result,callback-metadata,update-input,update-result}.

A11y-Audit-Ref: 4.1.2-codemirror-no-name

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
<div class="flex flex-col gap-2">
<h5>Input</h5>
<PayloadCodeBlock value={input} />
<PayloadCodeBlock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Type 'IPayloads | undefined' is not assignable to type 'object | IPayloads | IPayload'.

</p>
{#key activity.attempt}
<PayloadCodeBlock value={activity.heartbeatDetails} maxHeight={384} />
<PayloadCodeBlock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Type 'IPayloads | null | undefined' is not assignable to type 'object | IPayloads | IPayload'.

<div class="flex flex-col gap-2">
{#if searchAttributes}
<PayloadCodeBlock value={searchAttributes.indexedFields} />
<PayloadCodeBlock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Type 'Record<string, IPayload> | undefined' is not assignable to type 'object | IPayloads | IPayload'.

{translate('events.attribute-group.search-attributes')}
</h3>
<PayloadCodeBlock value={workflow.searchAttributes.indexedFields} />
<PayloadCodeBlock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Type 'Record<string, string> | undefined' is not assignable to type 'object | IPayloads | IPayload'.

label={translate('workflows.query-result')}
copyIconTitle={translate('common.copy-icon-title')}
copySuccessIconTitle={translate('common.copy-success-icon-title')}
testId="query-result"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Type 'false | "opacity-50"' is not assignable to type 'string | undefined'.

</h3>
{#if workflow?.searchAttributes}
<PayloadCodeBlock value={workflow.searchAttributes.indexedFields} />
<PayloadCodeBlock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Type 'Record<string, string> | undefined' is not assignable to type 'object | IPayloads | IPayload'.

@temporal-cicd

temporal-cicd Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

📊 Strict Mode: 86 errors in 19 files (9.5% of 904 total)

src/lib/holocene/code-block.svelte (6)
  • L146:32: 'editorView.contentHeight' is possibly 'undefined'.
  • L166:24: Type 'number | boolean' is not assignable to type 'boolean'.
  • L178:23: Type 'number | undefined' is not assignable to type 'number'.
  • L178:34: Type 'number | undefined' is not assignable to type 'number'.
  • L190:8: 'editorView' is possibly 'undefined'.
  • L298:30: Type 'boolean | 0' is not assignable to type 'boolean | undefined'.
src/lib/components/payload/payload-code-block.svelte (1)
  • L77:43: 'data.payloads' is possibly 'null' or 'undefined'.
src/lib/components/workflow/client-actions/update-confirmation-modal.svelte (4)
  • L40:38: Property 'failure' does not exist on type 'IOutcome | null | undefined'.
  • L43:38: Property 'success' does not exist on type 'IOutcome | null | undefined'.
  • L90:6: Type 'string | undefined' is not assignable to type 'string'.
  • L119:9: 'updateDefinitions.length' is possibly 'undefined'.
src/lib/components/event/event-card.svelte (11)
  • L141:37: Type 'string | null | undefined' is not assignable to type 'string'.
  • L141:48: 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • L149:6: Type 'string | null | undefined' is not assignable to type 'string'.
  • L149:15: 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • L152:10: 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • L176:19: Parameter 'key' implicitly has an 'any' type.
  • L176:24: Parameter 'value' implicitly has an 'any' type.
  • L226:15: Parameter 'key' implicitly has an 'any' type.
  • L226:20: Parameter 'value' implicitly has an 'any' type.
  • L246:18: Parameter 'key' implicitly has an 'any' type.
  • L246:23: Parameter 'value' implicitly has an 'any' type.
src/lib/components/workflow/pending-activity/pending-activity-card.svelte (8)
  • L175:8: Type 'IPayloads | null | undefined' is not assignable to type 'object | IPayloads | IPayload'.
  • L230:20: Parameter 'timeDifference' implicitly has an 'any' type.
  • L247:8: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number | null'.
  • L252:54: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
  • L31:26: 'activity.attempt' is possibly 'null' or 'undefined'.
  • L95:12: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
  • L97:14: Argument of type 'Duration | null' is not assignable to parameter of type 'string | Duration'.
  • L140:13: 'totalPending' is possibly 'undefined'.
src/lib/components/workflow/pending-nexus-operation/pending-nexus-operation-card.svelte (2)
  • L128:20: Parameter 'timeDifference' implicitly has an 'any' type.
  • L15:26: 'operation.attempt' is possibly 'null' or 'undefined'.
src/lib/components/schedule/schedule-frequency-panel.svelte (2)
  • L17:4: 'spec.structuredCalendar.length' is possibly 'undefined'.
  • L18:8: 'spec.structuredCalendar' is possibly 'null' or 'undefined'.
src/lib/components/schedule/schedule-form/schedules-calendar-view.svelte (1)
  • L113:27: Type 'IScheduleSpec | null | undefined' is not assignable to type 'IScheduleSpec'.
src/lib/components/standalone-activities/activity-input-and-outcome.svelte (1)
  • L20:6: Type 'IPayloads | undefined' is not assignable to type 'object | IPayloads | IPayload'.
src/lib/components/workflow/pending-activities.svelte (7)
  • L33:52: Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.
  • L37:5: 'pendingActivities' is possibly 'undefined'.
  • L44:29: 'pendingActivities' is possibly 'undefined'.
  • L56:27: 'pendingActivity.attempt' is possibly 'null' or 'undefined'.
  • L95:24: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number | null'.
  • L121:22: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
  • L123:24: Argument of type 'Duration | null' is not assignable to parameter of type 'string | Duration'.
src/lib/components/workflow/workflow-callback.svelte (8)
  • L38:11: Type 'null' cannot be used as an index type.
  • L38:11: Type 'undefined' cannot be used as an index type.
  • L52:17: 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • L53:36: Type 'string | null | undefined' is not assignable to type 'string'.
  • L53:47: 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • L61:15: 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • L62:34: Type 'string | null | undefined' is not assignable to type 'string'.
  • L62:45: 'link.workflowEvent' is possibly 'null' or 'undefined'.
src/lib/holocene/code-block.stories.svelte (10)
  • L84:44: Argument of type 'null' is not assignable to parameter of type '((key: string, value: { hello: string; }) => { hello: string; }) | undefined'.
  • L112:36: Argument of type 'null' is not assignable to parameter of type '((key: string, value: Record<string, unknown>) => Record<string, unknown>) | undefined'.
  • L133:53: Argument of type 'null' is not assignable to parameter of type '((key: string, value: { hello: string; }) => { hello: string; }) | undefined'.
  • L149:53: Argument of type 'null' is not assignable to parameter of type '((key: string, value: { hello: string; }) => { hello: string; }) | undefined'.
  • L157:53: Argument of type 'null' is not assignable to parameter of type '((key: string, value: { hello: string; }) => { hello: string; }) | undefined'.
  • L167:53: Argument of type 'null' is not assignable to parameter of type '((key: string, value: { hello: string; }) => { hello: string; }) | undefined'.
  • L177:6: Argument of type 'null' is not assignable to parameter of type '((key: string, value: TypedPropertyDescriptor[] & { [x: string]: PropertyDescriptor; }) => TypedPropertyDescriptor[] & { ...; }) | undefined'.
  • L189:6: Argument of type 'null' is not assignable to parameter of type '((key: string, value: TypedPropertyDescriptor[] & { [x: string]: PropertyDescriptor; }) => TypedPropertyDescriptor[] & { ...; }) | undefined'.
  • L345:8: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ 'File A': string; 'File B': string; }'.
  • L346:8: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ 'File A': string; 'File B': string; }'.
src/lib/pages/standalone-activity-details.svelte (9)
  • L38:6: '$activityExecution.info.attempt' is possibly 'null' or 'undefined'.
  • L81:9: '$activityExecution.info.activityType' is possibly 'null' or 'undefined'.
  • L104:16: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
  • L117:16: '$activityExecution.info.attempt' is possibly 'null' or 'undefined'.
  • L158:19: '$activityExecution.info.attempt' is possibly 'null' or 'undefined'.
  • L160:18: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
  • L285:16: Type 'string | null | undefined' is not assignable to type 'string'.
  • L287:14: Type 'string | null | undefined' is not assignable to type 'string'.
  • L296:14: Type 'string | null | undefined' is not assignable to type 'string'.
src/lib/pages/standalone-activity-search-attributes.svelte (1)
  • L11:6: Type 'Record<string, IPayload> | undefined' is not assignable to type 'object | IPayloads | IPayload'.
src/lib/pages/workflow-call-stack.svelte (3)
  • L26:6: Type 'WorkflowExecution | null' is not assignable to type 'Eventual<{ id: string; runId: string; }>'.
  • L45:30: 'workers.pollers.length' is possibly 'undefined'.
  • L109:12: Type 'string | undefined' is not assignable to type 'string | number'.
src/lib/pages/workflow-metadata.svelte (1)
  • L15:8: Type 'Record<string, string> | undefined' is not assignable to type 'object | IPayloads | IPayload'.
src/lib/pages/workflow-query.svelte (9)
  • L49:5: Type 'string | undefined' is not assignable to type 'string'.
  • L58:32: 'a.name' is possibly 'undefined'.
  • L59:32: 'b.name' is possibly 'undefined'.
  • L64:13: 'a.name' is possibly 'undefined'.
  • L64:34: No overload matches this call.
  • L96:6: Type 'Promise<IPayload[]> | Promise' is not assignable to type 'Promise'.
  • L113:6: Type '{ payloads: IPayloads; } | null' is not assignable to type 'IPayloads | undefined'.
  • L155:12: Type '"retry" | null' is not assignable to type '"search" | "link" | "success" | "error" | "action" | "activity" | "add-square" | "add" | "apple" | "archives" | "arrow-down" | "arrow-left" | "arrow-up" | "arrow-right" | "ascending" | ... 142 more ... | undefined'.
  • L184:12: Type 'false | "opacity-50"' is not assignable to type 'string | undefined'.
src/lib/pages/workflow-search-attributes.svelte (1)
  • L14:6: Type 'Record<string, string> | undefined' is not assignable to type 'object | IPayloads | IPayload'.
src/routes/(app)/namespaces/[namespace]/archival/+page.svelte (1)
  • L17:23: Property 'name' does not exist on type 'INamespaceInfo | null | undefined'.

Generated by 🚫 dangerJS against 393605e

code-block.stories.svelte: add label to "With Header" and "Test page
scrolling" stories which were missing the now-required prop.

compute-fields.svelte: add label to the Terraform IAM template CodeBlock
added in the serverless worker form refactor (landed in main after the
original PR was cut).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
label="Code example"
tabs={['File A', 'File B']}
bind:activeTab
content={hidden

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ 'File A': string; 'File B': string; }'.

@rosanusi rosanusi marked this pull request as ready for review June 15, 2026 16:45
@rosanusi rosanusi requested a review from a team as a code owner June 15, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant